package org.amphiprion.rfreplayanalyser.ui.race;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;

import javax.swing.JPanel;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;

import org.amphiprion.rfreplayanalyser.parser.DriverLocation;
import org.amphiprion.rfreplayanalyser.parser.Replay;
import org.amphiprion.rfreplayanalyser.parser.ReplayInfo;
import org.amphiprion.rfreplayanalyser.parser.Track;
import org.amphiprion.rfreplayanalyser.parser.WaypointAIWPath;
import org.amphiprion.rfreplayanalyser.util.MathUtil;
import org.amphiprion.rfreplayanalyser.util.RectangleCollisionUtil;
import org.amphiprion.rfreplayanalyser.util.RectangleCollisionUtil._Vector2D;

@SuppressWarnings("serial")
public class RaceDrawerPanel extends JPanel {
	private Stroke drawingStroke = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 9 }, 0);
	private Color ROAD_COLOR = Color.WHITE;
	private Color PITLANE_COLOR = new Color(250, 250, 250);
	private Color PITLANE_DASH_COLOR = new Color(230, 230, 230);

	private float zoom = 1;
	private RaceProvider raceProvider;
	private float replaytime = 0;
	private RectangleCollisionUtil util = new RectangleCollisionUtil();

	public enum MouseMode {
		MOVE, ZOOM
	}

	private MouseMode mouseMode;
	private float circuitWidthX = 0;
	private float circuitWidthY = 0;

	/**
	 * Create the panel.
	 */
	public RaceDrawerPanel(final RaceProvider raceProvider) {
		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(final MouseEvent arg0) {
				if (mouseMode == MouseMode.ZOOM) {
					Replay replay = raceProvider.getReplay();
					final ReplayInfo info = replay.getInfo();
					final float ratio = Math.min(((JViewport) getParent()).getWidth() / circuitWidthX, ((JViewport) getParent()).getHeight() / circuitWidthY);
					final float realX = arg0.getX() / (ratio * zoom) + info.getMinPosX();
					final float realY = arg0.getY() / (ratio * zoom) + info.getMinPosY();
					float newZoom = zoom;
					if (arg0.getButton() == 1) {
						newZoom = zoom + 1f;
					} else if (arg0.getButton() == 3) {
						newZoom = zoom - 1f;
					}
					final float fzoom = newZoom;
					setZoom(newZoom);
					SwingUtilities.invokeLater(new Runnable() {

						@Override
						public void run() {
							int newX = (int) ((realX - info.getMinPosX()) * ratio * fzoom);
							int newY = (int) ((realY - info.getMinPosY()) * ratio * fzoom);

							raceProvider.updateScrollbarPositionToCenterAt(newX, newY);
						}
					});
				} else {
					// selection de la courbe
					int px = arg0.getX();
					int py = arg0.getY();

					Replay replay = raceProvider.getReplay();
					ReplayInfo info = replay.getInfo();

					circuitWidthX = info.getMaxPosX() - info.getMinPosX();
					circuitWidthY = info.getMaxPosY() - info.getMinPosY();
					float ratio = Math.min(((JViewport) getParent()).getWidth() / circuitWidthX, ((JViewport) getParent()).getHeight() / circuitWidthY);
					float realX = px / (ratio * zoom) + info.getMinPosX();
					float realY = py / (ratio * zoom) + info.getMinPosY();
					findDriverFromScreenCoord(realX, realY);
				}
			}
		});
		this.raceProvider = raceProvider;
		setMouseMode(MouseMode.ZOOM);
	}

	public void setReplaytime(float replaytime) {
		this.replaytime = replaytime;
		revalidate();
		repaint();
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		Replay replay = raceProvider.getReplay();
		ReplayInfo info = replay.getInfo();

		paintTrack(g);

		_Vector2D vtl = util.new _Vector2D();
		_Vector2D vtr = util.new _Vector2D();
		_Vector2D vbl = util.new _Vector2D();
		_Vector2D vbr = util.new _Vector2D();

		Color lineColor;
		circuitWidthX = info.getMaxPosX() - info.getMinPosX();
		circuitWidthY = info.getMaxPosY() - info.getMinPosY();
		int viewPortWidth = ((JViewport) getParent()).getWidth();
		int viewPortHeight = ((JViewport) getParent()).getHeight();
		float ratio = Math.min(viewPortWidth / circuitWidthX, viewPortHeight / circuitWidthY);
		int lapItemIndex = 0;
		for (DriverItem driverItem : raceProvider.getDriverItems()) {
			if (!driverItem.running || !driverItem.shown) {
				continue;
			}
			if (!raceProvider.isDriverItemSelected(driverItem)) {
				lineColor = new Color(driverItem.color.getRed(), driverItem.color.getGreen(), driverItem.color.getBlue(), 50);
			} else {
				lineColor = driverItem.color;
			}
			g.setColor(lineColor);

			boolean first = true;
			int lastX = 0;
			int lastY = 0;
			float lastReplaytime = 0;
			DriverLocation lastLoc = null;
			List<DriverLocation> locs = replay.getDriverLocations(driverItem.driverId);
			for (DriverLocation loc : locs) {
				if (loc.getLap() == driverItem.currentLap || lastLoc != null && lastLoc.getLap() == driverItem.currentLap) {
					int newX = (int) ((loc.getPosX() - info.getMinPosX()) * ratio * zoom);
					int newY = (int) ((loc.getPosY() - info.getMinPosY()) * ratio * zoom);
					float newReplayTime = loc.getReplayTime();
					if (!first) {
						g.drawLine(lastX, lastY, newX, newY);

						if (newReplayTime >= replaytime && lastReplaytime <= replaytime) {
							float timeratio = (replaytime - lastReplaytime) / (newReplayTime - lastReplaytime);
							int carX = (int) (lastX + (newX - lastX) * timeratio);
							int carY = (int) (lastY + (newY - lastY) * timeratio);
							g.setColor(driverItem.color);

							if (!raceProvider.isDriverItemSelected(driverItem)) {
								int sizeCar = Math.max(10, (int) (10 * zoom / 11));
								g.fillArc(carX - sizeCar / 2, carY - sizeCar / 2, sizeCar, sizeCar, 0, 360);
							} else {
								int sizeCar = Math.max(10, (int) (10 * zoom / 11));
								g.fillRect(carX - sizeCar / 2, carY - sizeCar / 2, sizeCar, sizeCar);
								// System.out.println("locX=" + loc.getPosX());
								g.setColor(Color.BLACK);
								g.drawRect(carX - sizeCar / 2, carY - sizeCar / 2, sizeCar, sizeCar);

								g.drawLine(carX, carY, carX + (int) (sizeCar * Math.sin(loc.angZ)), carY - (int) (sizeCar * Math.cos(loc.angZ)));

							}

							g.setColor(lineColor);

							vtl.x = -replay.minWithToTriggerTouchEvent * ratio * zoom / 2;
							vtl.y = +replay.minHeightToTriggerTouchEvent * ratio * zoom / 2;
							util.RotateVector2DClockwise(vtl, -loc.angZ);

							vtr.x = +replay.minWithToTriggerTouchEvent * ratio * zoom / 2;
							vtr.y = +replay.minHeightToTriggerTouchEvent * ratio * zoom / 2;
							util.RotateVector2DClockwise(vtr, -loc.angZ);

							vbr.x = +replay.minWithToTriggerTouchEvent * ratio * zoom / 2;
							vbr.y = -replay.minHeightToTriggerTouchEvent * ratio * zoom / 2;
							util.RotateVector2DClockwise(vbr, -loc.angZ);

							vbl.x = -replay.minWithToTriggerTouchEvent * ratio * zoom / 2;
							vbl.y = -replay.minHeightToTriggerTouchEvent * ratio * zoom / 2;
							util.RotateVector2DClockwise(vbl, -loc.angZ);
							g.drawLine((int) (carX + vtl.x), (int) (carY + vtl.y), (int) (carX + vtr.x), (int) (carY + vtr.y));
							g.drawLine((int) (carX + vtr.x), (int) (carY + vtr.y), (int) (carX + vbr.x), (int) (carY + vbr.y));
							g.drawLine((int) (carX + vbr.x), (int) (carY + vbr.y), (int) (carX + vbl.x), (int) (carY + vbl.y));
							g.drawLine((int) (carX + vbl.x), (int) (carY + vbl.y), (int) (carX + vtl.x), (int) (carY + vtl.y));

							if (raceProvider.isDriverItemSelected(driverItem)) {
								raceProvider.updateScreenPositionSelectedCar(new Point(carX, carY));
								// ((JViewport)
								// getParent()).setViewPosition(new
								// Point(carX - viewPortWidth / 2, carY -
								// viewPortHeight / 2));

							}
						}

					} else {
						first = false;
					}

					lastX = newX;
					lastY = newY;
					lastReplaytime = newReplayTime;
					lastLoc = loc;
				}
			}
			lapItemIndex++;
		}

	}

	private void paintTrack(Graphics g) {
		Replay replay = raceProvider.getReplay();

		Track track = replay.getTrack();
		if (track == null) {
			return;
		}
		ReplayInfo info = replay.getInfo();
		circuitWidthX = info.getMaxPosX() - info.getMinPosX();
		circuitWidthY = info.getMaxPosY() - info.getMinPosY();
		int viewPortWidth = ((JViewport) getParent()).getWidth();
		int viewPortHeight = ((JViewport) getParent()).getHeight();
		float ratio = Math.min(viewPortWidth / circuitWidthX, viewPortHeight / circuitWidthY);
		Graphics2D g2 = (Graphics2D) g;
		BasicStroke lineStroke = new BasicStroke(1f);
		g.setColor(PITLANE_COLOR);
		for (int iPit = 1; iPit <= track.nbPitLanes; iPit++) {
			List<WaypointAIWPath> pitlane = track.pitLanePathById.get(iPit);
			WaypointAIWPath newPath;
			int[] xpoints = new int[pitlane.size() * 2 + 1];
			int[] ypoints = new int[pitlane.size() * 2 + 1];
			for (int i = 0; i < pitlane.size(); i++) {
				newPath = pitlane.get(i);

				int newX = (int) ((newPath.pos.x - newPath.perp.x * newPath.roadLeft - info.getMinPosX()) * ratio * zoom);
				int newY = (int) ((newPath.pos.y - newPath.perp.y * newPath.roadLeft - info.getMinPosY()) * ratio * zoom);
				// g.drawLine(lastX, lastY, newX, newY);
				xpoints[i] = newX;
				ypoints[i] = newY;
				if (i == 0) {
					xpoints[xpoints.length - 1] = newX;
					ypoints[xpoints.length - 1] = newY;
				}
				newX = (int) ((newPath.pos.x + newPath.perp.x * newPath.roadRight - info.getMinPosX()) * ratio * zoom);
				newY = (int) ((newPath.pos.y + newPath.perp.y * newPath.roadRight - info.getMinPosY()) * ratio * zoom);
				// g.drawLine(lastX, lastY, newX, newY);
				xpoints[xpoints.length - 2 - i] = newX;
				ypoints[xpoints.length - 2 - i] = newY;
			}
			g.fillPolygon(xpoints, ypoints, xpoints.length);
		}

		// pitlane standard width
		g2.setStroke(drawingStroke);
		g.setColor(PITLANE_DASH_COLOR);
		float pitHalfWidth = track.laneSpacing / 2;
		for (int iPit = 1; iPit <= track.nbPitLanes; iPit++) {
			List<WaypointAIWPath> pitlane = track.pitLanePathById.get(iPit);
			WaypointAIWPath lastPath = pitlane.get(0);
			WaypointAIWPath newPath;
			for (int i = 1; i < pitlane.size(); i++) {
				newPath = pitlane.get(i);

				int lastX = (int) ((lastPath.pos.x - lastPath.perp.x * pitHalfWidth - info.getMinPosX()) * ratio * zoom);
				int lastY = (int) ((lastPath.pos.y - lastPath.perp.y * pitHalfWidth - info.getMinPosY()) * ratio * zoom);
				int newX = (int) ((newPath.pos.x - newPath.perp.x * pitHalfWidth - info.getMinPosX()) * ratio * zoom);
				int newY = (int) ((newPath.pos.y - newPath.perp.y * pitHalfWidth - info.getMinPosY()) * ratio * zoom);
				g.drawLine(lastX, lastY, newX, newY);

				lastX = (int) ((lastPath.pos.x + lastPath.perp.x * pitHalfWidth - info.getMinPosX()) * ratio * zoom);
				lastY = (int) ((lastPath.pos.y + lastPath.perp.y * pitHalfWidth - info.getMinPosY()) * ratio * zoom);
				newX = (int) ((newPath.pos.x + newPath.perp.x * pitHalfWidth - info.getMinPosX()) * ratio * zoom);
				newY = (int) ((newPath.pos.y + newPath.perp.y * pitHalfWidth - info.getMinPosY()) * ratio * zoom);
				g.drawLine(lastX, lastY, newX, newY);

				lastPath = newPath;
			}
		}

		g2.setStroke(lineStroke);
		g.setColor(ROAD_COLOR);
		int[] xpoints = new int[track.trackDrivingPath.size() * 2 + 3];
		int[] ypoints = new int[track.trackDrivingPath.size() * 2 + 3];
		{
			WaypointAIWPath newPath;
			for (int i = 0; i < track.trackDrivingPath.size(); i++) {
				newPath = track.trackDrivingPath.get(i);

				int newX = (int) ((newPath.pos.x - newPath.perp.x * newPath.roadLeft - info.getMinPosX()) * ratio * zoom);
				int newY = (int) ((newPath.pos.y - newPath.perp.y * newPath.roadLeft - info.getMinPosY()) * ratio * zoom);
				// g.drawLine(lastX, lastY, newX, newY);
				xpoints[i] = newX;
				ypoints[i] = newY;
				if (i == 0) {
					xpoints[track.trackDrivingPath.size()] = newX;
					ypoints[track.trackDrivingPath.size()] = newY;
					xpoints[xpoints.length - 1] = newX;
					ypoints[xpoints.length - 1] = newY;
				}
				newX = (int) ((newPath.pos.x + newPath.perp.x * newPath.roadRight - info.getMinPosX()) * ratio * zoom);
				newY = (int) ((newPath.pos.y + newPath.perp.y * newPath.roadRight - info.getMinPosY()) * ratio * zoom);
				// g.drawLine(lastX, lastY, newX, newY);
				xpoints[xpoints.length - 2 - i] = newX;
				ypoints[xpoints.length - 2 - i] = newY;
				if (i == 0) {
					xpoints[track.trackDrivingPath.size() + 1] = newX;
					ypoints[track.trackDrivingPath.size() + 1] = newY;
				}
			}
		}
		g.fillPolygon(xpoints, ypoints, xpoints.length);
	}

	public void setMouseMode(MouseMode mode) {
		this.mouseMode = mode;
		if (mode == MouseMode.ZOOM) {
			// Cursor cursor =
			// Toolkit.getDefaultToolkit().createCustomCursor(img, hotSpot,
			// name);
			// component.setCursor(cursor);

			setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
		} else {
			setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
		}
	}

	private void setZoom(float zoom) {
		this.zoom = Math.max(1, zoom);
		float ratio = Math.min(((JViewport) getParent()).getWidth() / circuitWidthX, ((JViewport) getParent()).getHeight() / circuitWidthY);
		this.setPreferredSize(new Dimension((int) (circuitWidthX * ratio * zoom), (int) (circuitWidthY * ratio * zoom)));
		this.setMinimumSize(new Dimension((int) (circuitWidthX * ratio * zoom), (int) (circuitWidthY * ratio * zoom)));
		((JViewport) getParent()).revalidate();
		((JViewport) getParent()).repaint();
		// revalidate();
		// repaint();
		// System.out.println(zoom);
	}

	public void toogleZoomMouse(boolean selected) {
		if (selected) {
			setMouseMode(MouseMode.ZOOM);
		} else {
			setMouseMode(MouseMode.MOVE);
		}
	}

	private void findDriverFromScreenCoord(float posX, float posY) {
		Replay replay = raceProvider.getReplay();
		ReplayInfo info = replay.getInfo();
		circuitWidthX = info.getMaxPosX() - info.getMinPosX();
		circuitWidthY = info.getMaxPosY() - info.getMinPosY();
		int viewPortWidth = ((JViewport) getParent()).getWidth();
		int viewPortHeight = ((JViewport) getParent()).getHeight();
		float ratio = Math.min(viewPortWidth / circuitWidthX, viewPortHeight / circuitWidthY);

		double[] pA = new double[2];
		double[] pB = new double[2];
		double[] pM = new double[] { posX, posY };
		DriverItem foundDriverItem = null;
		double minDist = Double.MAX_VALUE;
		for (DriverItem driverItem : raceProvider.getDriverItems()) {
			if (!driverItem.running) {
				continue;
			}
			DriverLocation lastLoc = null;
			boolean first = true;
			List<DriverLocation> locs = replay.getDriverLocations(driverItem.driverId);
			for (DriverLocation loc : locs) {
				if (loc.getLap() == driverItem.currentLap) {
					pB[0] = loc.getPosX();
					pB[1] = loc.getPosY();
					if (!first) {
						double dist = MathUtil.LineToPointDistance2D(pA, pB, pM, true);

						if (dist < minDist) {
							minDist = dist;
							foundDriverItem = driverItem;
						}
					} else {
						first = false;
					}

					pA[0] = pB[0];
					pA[1] = pB[1];
					lastLoc = loc;
				}
			}

		}
		if (foundDriverItem != null) {
			raceProvider.driverItemSelected(foundDriverItem, false);
		}
	}
}
